//
//  Author(s):
//
//      Scott Wilson (sw@scratchstudio.net)
//	
//  A contribution to ELMAH originally written by:
//
//      Atif Aziz (atif.aziz@skybow.com, http://www.raboof.com)
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 

using System;
using System.IO;
using System.Xml;
using System.Web;
using System.Collections;
using Elmah;

namespace Elmah
{
	/// <summary>
	/// An <see cref="ErrorLog"/> implementation that uses XML files stored on disk 
	/// as its backing store.
	/// </summary>
	public class XmlErrorLog : Elmah.ErrorLog
	{
		private string LogPath;

		/// <summary>
		/// Initializes a new instance of the <see cref="SqlErrorLog"/> class
		/// using a dictionary of configured settings.
		/// </summary>
		public XmlErrorLog(IDictionary config)
		{
			if (config["LogPath"] != null)
			{
				LogPath = (string)config["LogPath"];
			}
			else
			{
				throw new System.ApplicationException("Log Path is missing for the Xml error log.");
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="SqlErrorLog"/> class
		/// to use a specific path to store/load XML files.
		/// </summary>
		public XmlErrorLog(string logPath)
		{
			LogPath = logPath;
		}

		/// <summary>
		/// Gets the name of this error log implementation.
		/// </summary>
		public override string Name
		{
			get { return "Xml File Error Log"; }
		}

		/// <summary>
		/// Logs an error to the database.
		/// </summary>
		/// <remarks>
		/// Logs an error as a single XML file stored in a folder. XML files are named with a
		/// sortable date and a unique identifier. Currently the XML files are stored indefinately.
		/// As they are stored as files, they may be managed using standard scheduled jobs.
		/// </remarks>
		public override string Log(Elmah.Error error)
		{
			Guid errorId = Guid.NewGuid();
			string timeStamp = DateTime.Now.ToString("u");
			timeStamp = timeStamp.Replace(":", "");
			timeStamp = timeStamp.Replace(" ", "");
			string fileName = string.Format(@"{0}\error-{1}-{2}.xml", LogPath, timeStamp, errorId.ToString());
			
			FileInfo outfile = new FileInfo(fileName);
			StreamWriter outstream = outfile.CreateText();
			XmlTextWriter errorXmlWriter = new XmlTextWriter(outstream);
			errorXmlWriter.Formatting = Formatting.Indented;

			errorXmlWriter.WriteStartElement("error");
			errorXmlWriter.WriteAttributeString("errorId", errorId.ToString());
			error.ToXml(errorXmlWriter);
			errorXmlWriter.WriteEndElement();
			errorXmlWriter.Flush();
		
			errorXmlWriter.Close();
			outstream.Close();
		    return string.Empty;

		}

		/// <summary>
		/// Returns a page of errors from the folder in descending order 
		/// of logged time as defined by the sortable filenames.
		/// </summary>
		public override int GetErrors(int pageIndex, int pageSize, IList errorEntryList)
		{
			if (pageIndex < 0)
			{
				throw new ArgumentOutOfRangeException("pageIndex");
			}

			if (pageSize < 0)
			{
				throw new ArgumentOutOfRangeException("pageSize");
			}

			/* Get the file list from the folder */
			string[] fileList = Directory.GetFiles(LogPath);

			if (fileList.Length < 1)
			{
				return 0;
			}

			Array.Sort(fileList);
			Array.Reverse(fileList);
			
			/* Find the proper page */
			int currentItem = pageIndex * pageSize;
			int lastItem = (currentItem + pageSize < fileList.Length) ? currentItem + pageSize : fileList.Length;

			/* Open them up and rehydrate the list */
			for (int i = currentItem; i < lastItem; i++)
			{
				FileInfo f = new FileInfo(fileList[i]);
				FileStream s = f.OpenRead();
				XmlTextReader reader = new XmlTextReader(s);

				try
				{
					while (reader.IsStartElement("error"))
					{
						string id = reader.GetAttribute("errorId");
                        
						Elmah.Error error = new Elmah.Error();
						error.FromXml(reader);

						if (errorEntryList != null)
						{
							errorEntryList.Add(new Elmah.ErrorLogEntry(this, id, error));
						}
					}
				}
				finally
				{
					reader.Close();
				}

			}
	
			/* Return how many are total */
			return fileList.Length;
		}

		/// <summary>
		/// Returns the specified error from the filesystem, or throws an exception if it does not exist.
		/// </summary>
		public override Elmah.ErrorLogEntry GetError(string id)
		{
			/* Get the file folder list - should only return one ever */
			string[] fileList = Directory.GetFiles(LogPath, string.Format("*{0}.xml", id));
			
			if (fileList.Length < 1)
			{
				throw new System.ApplicationException(string.Format("Can't locate error file for errorId {0}", id));
			}

			FileInfo f = new FileInfo(fileList[0]);
			FileStream s = f.OpenRead();
			XmlTextReader reader = new XmlTextReader(s);
			Elmah.Error error = new Elmah.Error();
			error.FromXml(reader);
			reader.Close();
			return new Elmah.ErrorLogEntry(this, id, error);
		}
	}
}
